package com.example.lottery.service.impl;

import com.example.lottery.common.errorcode.ServiceErrorCodeConstants;
import com.example.lottery.common.utils.JacksonUtil;
import com.example.lottery.common.utils.RedisUtil;
import com.example.lottery.controller.param.DrawPrizeParam;
import com.example.lottery.controller.param.ShowWinningRecordsParam;
import com.example.lottery.dao.datobject.*;
import com.example.lottery.dao.mapper.*;
import com.example.lottery.service.DrawPrizeService;
import com.example.lottery.service.dto.WinningRecordDTO;
import com.example.lottery.service.enums.ActivityPrizeStatusEnum;
import com.example.lottery.service.enums.ActivityPrizeTiersEnum;
import com.example.lottery.service.enums.ActivityStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Param;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.example.lottery.common.config.DirectRabbitConfig.EXCHANGE_NAME;
import static com.example.lottery.common.config.DirectRabbitConfig.ROUTING;

@Slf4j
@Service
public class DrawPrizeServiceImpl implements DrawPrizeService {
    private final String WINNING_RECORDS_PREFIX = "WINNING_RECORDS_";
    /**
     * 2天
     */
    private final Long WINNING_RECORDS_TIMEOUT = 60 * 60 * 24 * 2L;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ActivityMapper activityMapper;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PrizeMapper prizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;
    @Autowired
    private RedisUtil redisUtil;
    @Override
    public void drawPrize(DrawPrizeParam param) {
        Map<String,String> map=new HashMap<>();
        map.put("messageId",String.valueOf(UUID.randomUUID()));
        map.put("messageData", JacksonUtil.writeValueAsString(param));
        //发信息:交换机 绑定的key，消息体
        rabbitTemplate.convertAndSend(EXCHANGE_NAME,ROUTING,map);
        log.info("mq消息发送成功:map={}",JacksonUtil.writeValueAsString(map));
    }

    @Override
    public boolean checkDrawPrizeParam(DrawPrizeParam param) {
        ActivityDO activityDO=activityMapper.selectById(param.getActivityId());
        //奖品是否存在 可以从activity_prize里面获得，因为createActivity做了本地事务，保证了一致性
        ActivityPrizeDO activityPrizeDO=activityPrizeMapper.selectByAPId(
                param.getActivityId(),param.getPrizeId());
        //活动或者奖品不存在
        if(activityDO==null||activityPrizeDO==null){
            log.info("校验抽奖请求失败! 失败原因:{}",
                    ServiceErrorCodeConstants.ACTIVITY_OR_PRIZE_IS_EMPTY.getMsg());
            return false;
        }
        //活动是否有效
        if(activityDO.getStatus()
                .equalsIgnoreCase(ActivityStatusEnum.COMPLETED.name())){
            log.info("校验抽奖请求失败! 失败原因:{}",
                    ServiceErrorCodeConstants.ACTIVITY_COMPLETED.getMsg());
            return false;
        }
        //奖品是否有效
        if(activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name())){
            log.info("校验抽奖请求失败! 失败原因:{}",
                    ServiceErrorCodeConstants.ACTIVITY_PRIZE_COMPLETED.getMsg());
            return false;
        }
        //中奖者人数是否和设置的奖品数量一致
        if(activityPrizeDO.getPrizeAmount()!=param.getWinnerList().size()){
            log.info("校验抽奖请求失败! 失败原因:{}",
                    ServiceErrorCodeConstants.WINNER_PRIZE_AMOUNT_ERROR.getMsg());
            return false;
        }
        return true;
    }

    @Override
    public List<WinningRecordDO> saveWinnerRecords(DrawPrizeParam param) {
        //查询相关信息 活动 人员 活动奖品 奖品
        ActivityDO activityDO=activityMapper.selectById(param.getActivityId());
        List<UserDO> userDOList=userMapper.batchSelectByIds(
                param.getWinnerList()
                        .stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        PrizeDO prizeDO=prizeMapper.selectById(param.getPrizeId());
        ActivityPrizeDO activityPrizeDO=
                activityPrizeMapper.selectByAPId(param.getActivityId(),param.getPrizeId());

        //构造中奖者记录，进行保存
        List<WinningRecordDO> winningRecordDOList=userDOList.stream()
                .map(userDO -> {
                    WinningRecordDO winningRecordDO=new WinningRecordDO();
                    winningRecordDO.setActivityId(activityDO.getId());
                    winningRecordDO.setActivityName(activityDO.getActivityName());
                    winningRecordDO.setPrizeId(prizeDO.getId());
                    winningRecordDO.setPrizeName(prizeDO.getName());
                    winningRecordDO.setPrizeTier(activityPrizeDO.getPrizeTiers());
                    winningRecordDO.setWinnerId(userDO.getId());
                    winningRecordDO.setWinnerName(userDO.getUserName());
                    winningRecordDO.setWinnerEmail(userDO.getEmail());
                    winningRecordDO.setWinnerPhoneNumber(userDO.getPhoneNumber());
                    winningRecordDO.setWinningTime(param.getWinningTime());
                    return winningRecordDO;
                }).collect(Collectors.toList());
        winningRecordMapper.batchInsert(winningRecordDOList);
        // 缓存中奖者记录
        // 1、缓存奖品维度中奖记录key->前缀+WinningRecord_activityId_prizeId, value->winningRecordDOList（奖品维度的中奖名单）)
        cacheWinningRecords(param.getActivityId() + "_" + param.getPrizeId(),
                winningRecordDOList,
                WINNING_RECORDS_TIMEOUT);
        //2.缓存活动维度中奖记录
        //key->前缀+WinningRecord_activityId, value->winningRecordDOList（活动维度的中奖名单）)
        //当活动已经完成 再去存放 活动维度中奖记录
        if(activityDO.getStatus()
                .equalsIgnoreCase(ActivityStatusEnum.COMPLETED.name())){
            //查询活动维度的全量中奖记录
            List<WinningRecordDO> allList=winningRecordMapper.selectByActivityId(param.getActivityId());
            cacheWinningRecords(String.valueOf(param.getActivityId()),allList,WINNING_RECORDS_TIMEOUT);
        }
        return winningRecordDOList;
    }

    /**
     * 缓存中奖记录
     * @param key
     * @param winningRecordDOList
     * @param time
     */
    private void cacheWinningRecords(String key, List<WinningRecordDO> winningRecordDOList, Long time) {
        String str = "";
        try{
            if(!StringUtils.hasText(key)
            || CollectionUtils.isEmpty(winningRecordDOList)){
                log.warn("要缓存的内容为空! key:{},value:{}",key,JacksonUtil.writeValueAsString(winningRecordDOList));
                return;
            }
            str=JacksonUtil.writeValueAsString(winningRecordDOList);
            redisUtil.set(WINNING_RECORDS_PREFIX+key,str,time);
        }catch (Exception e){
            log.error("缓存中奖记录异常! key:{},value:{}",WINNING_RECORDS_PREFIX+key,str);
        }

    }

    /**
     * 删除活动维度或者奖品维度下的中奖记录
     * @param activityId
     * @param prizeId
     */
    @Override
    public void deleteRecords(Long activityId, Long prizeId) {
        if(activityId==null){
            log.warn("要删除中奖记录相关的活动id为空!");
            return;
        }
        //删除数据表
        //奖品或者活动维度下的 prizeId为null和不为null都要删 写一个动态sql
        //prizeId 为null 活动维度
        //peizeId 不为null 奖品维度
        winningRecordMapper.deleteRecords(activityId,prizeId);
        //删除缓存（奖品维度和活动维度的）
        if(prizeId!=null){
            //删除奖品维度的缓存
            deleteWinningRecords(activityId+"_"+prizeId);
        }
        //删除活动维度的中奖记录缓存
        deleteWinningRecords(String.valueOf(activityId));
    }

    /**
     * 从缓存中删除中奖记录
     * @param key
     */
    private void deleteWinningRecords(String key) {
        try{
            if(redisUtil.hasKey(WINNING_RECORDS_PREFIX+key)){
                redisUtil.del(WINNING_RECORDS_PREFIX+key);
            }
        }catch (Exception e){
            log.error("删除中奖记录缓存异常,key:{}",key);
        }
    }

    @Override
    public List<WinningRecordDTO> getRecords(ShowWinningRecordsParam param) {
        //从redis里面查询 活动维度的和奖品维度的
        String key=null==param.getPrizeId()
                ? String.valueOf(param.getActivityId())
                :param.getActivityId()+"_"+param.getPrizeId();
        List<WinningRecordDO> winningRecordDOList=getWinningRecords(key);
        if(!CollectionUtils.isEmpty(winningRecordDOList)){
            return convertToWinningRecordDTOList(winningRecordDOList);
        }
        //如果在redis中不存在，查库
        winningRecordDOList=winningRecordMapper.selectByActivityIdOrPrizeId(
                param.getActivityId(),param.getPrizeId());
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            log.info("查询的中奖记录为空! param:{}",JacksonUtil.writeValueAsString(param));
            return Arrays.asList();
        }
        //存放记录到redis
        cacheWinningRecords(key,winningRecordDOList,WINNING_RECORDS_TIMEOUT);
        return convertToWinningRecordDTOList(winningRecordDOList);
    }

    private List<WinningRecordDTO> convertToWinningRecordDTOList(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            return Arrays.asList();
        }
        return winningRecordDOList.stream()
                .map(winningRecordDO -> {
                    WinningRecordDTO winningRecordDTO=new WinningRecordDTO();
                    winningRecordDTO.setWinnerId(winningRecordDO.getWinnerId());
                    winningRecordDTO.setWinnerName(winningRecordDO.getWinnerName());
                    winningRecordDTO.setPrizeName(winningRecordDO.getPrizeName());
                    winningRecordDTO.setPrizeTier(ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()));
                    winningRecordDTO.setWinningTime(winningRecordDO.getWinningTime());
                    return winningRecordDTO;
                }).collect(Collectors.toList());
    }

    /**
     * 从缓存中获取记录
     * @param key
     * @return
     */
    private List<WinningRecordDO> getWinningRecords(String key) {
       try{
           if(!StringUtils.hasText(key)){
               log.warn("要从缓存中查询中奖记录的key为空!");
           }
           String str=redisUtil.get(WINNING_RECORDS_PREFIX+key);
           if(!StringUtils.hasText(str)){
               return Arrays.asList();
           }
           return JacksonUtil.readListValue(str,WinningRecordDO.class);

       }catch (Exception e){
           log.error("从缓存中查询中奖记录异常! key:{}",WINNING_RECORDS_PREFIX+key);
           return Arrays.asList();

       }
    }


}
